//
// Created by suzhen on 16-11-18.
//

#ifndef OKPG_OKPGCONPOOL_H
#define OKPG_OKPGCONPOOL_H

#include <folly/futures/Future.h>

namespace OKPg {

    template<typename Tp>
    class OkPoolUnique {

        using OkPoolUniqueContentDeleter =
        std::function<void(Tp &)>;

    private:
        OkPoolUniqueContentDeleter contentDeleter_;
        Tp *content_;

    public:

        explicit OkPoolUnique(OkPoolUniqueContentDeleter &&d, Tp *ptr) :
                contentDeleter_(std::forward<OkPoolUniqueContentDeleter>(d)),
                content_(ptr) {
        }

        template<typename... Args>
        explicit OkPoolUnique(OkPoolUniqueContentDeleter &&d, Args &&... args):
                contentDeleter_(std::forward<OkPoolUniqueContentDeleter>(d)),
                content_(new Tp(std::forward<Args>(args)...)) {
        }

        virtual ~OkPoolUnique() {
            if (!content_) {
                return;
            }
            contentDeleter_(*content_);
        }

        Tp &getContent() {
            return *content_;
        }

        OkPoolUnique(OkPoolUnique &&o) :
                content_(o.content_),
                contentDeleter_(std::move(o.contentDeleter_)) {
            o.content_ = nullptr;
        }

        OkPoolUnique &operator=(OkPoolUnique &&o) {
            if (this == &o) {
                return *this;
            }
            if (content_) {
                contentDeleter_(*content_);
            }
            content_ = o.content_;
            o.content_ = nullptr;
            contentDeleter_ = std::move(o.contentDeleter_);
            return *this;
        }

        //none-copyable

        OkPoolUnique(const OkPoolUnique &) = delete;

        OkPoolUnique &operator=(const OkPoolUnique &) = delete;

    };

    template<typename T>
    class OkPoolTypes;

    template<typename T>
    class OkPool;

    template<typename T>
    class OkPoolItem;

    template<typename ItemTp>
    struct OkPoolTypes {
        using ItemType = ItemTp;
        using RawItemUniqueType = OkPoolUnique<ItemTp>;
        using PoolItem = OkPoolItem<ItemType>;
        using FuturePoolItem = folly::Future<PoolItem>;
        using PromisePoolItem = folly::Promise<PoolItem>;
        using RawItemBuilderFunType = std::function<RawItemUniqueType()>;
    };

    template<typename ItemTp>
    class OkPoolItem {
        friend class OkPool<ItemTp>;

    public:
        using OkPoolItemTypeDefs =  OkPoolTypes<ItemTp>;
    private:
        OkPool<ItemTp> *parentPool_;
        typename OkPoolItemTypeDefs::RawItemUniqueType rawItem_;

    public:
        OkPoolItem(
                OkPool<ItemTp> &parentPool,
                typename OkPoolItemTypeDefs::RawItemUniqueType &&p)
                : parentPool_(&parentPool),
                  rawItem_(std::forward<typename OkPoolItemTypeDefs::RawItemUniqueType>(p)) {
        }

        typename OkPoolItemTypeDefs::ItemType &getContent() {
            return rawItem_.getContent();
        }

        void swapContent(typename OkPoolItemTypeDefs::RawItemUniqueType o) {
            rawItem_ = std::move(o);
        }

        OkPoolItem(const OkPoolItem &) = delete;

        OkPoolItem &operator=(const OkPoolItem &) = delete;

        OkPoolItem moveOut() {
            OkPoolItem out(*parentPool_, std::move(rawItem_));
            parentPool_ = nullptr;
            return out;
        }

        OkPoolItem(OkPoolItem &&o)
                : parentPool_(o.parentPool_),
                  rawItem_(std::move(o.rawItem_)) {
            o.parentPool_ = nullptr;
        }

        OkPoolItem &operator=(OkPoolItem &&o) {
            if (this == &o) {
                return *this;
            }
            parentPool_ = o.parentPool_;
            o.parentPool_ = nullptr;
            rawItem_ = std::move(o.rawItem_);
            return *this;
        }
    };

    template<typename ItemTp>
    class OkPool {

    public:
        using OkPgPoolTypedefs = OkPoolTypes<ItemTp>;

    private:
        size_t poolSize_;
        typename OkPgPoolTypedefs::RawItemBuilderFunType builder_;
        std::queue<typename OkPgPoolTypedefs::PoolItem> itemContainer_;
        std::queue<typename OkPgPoolTypedefs::PromisePoolItem> waitingContainer_;
        std::recursive_mutex poolLock_;

    public:
        virtual ~OkPool() {
            CHECK(itemContainer_.size() == poolSize_) << "YOUR RESOURCE LEAK ! CHECK YOUR CODE !";
        }

        OkPool(size_t poolSize,
               typename OkPgPoolTypedefs::RawItemBuilderFunType &&builder) :
                poolSize_(poolSize),
                builder_(std::forward<typename OkPoolTypes<ItemTp>::RawItemBuilderFunType>(builder)) {
            //ctor
            for (size_t i = 0; i < poolSize; ++i) {
                typename OkPgPoolTypedefs::RawItemUniqueType shareRawItem = builder_();
                typename OkPgPoolTypedefs::PoolItem item(*this, std::move(shareRawItem));
                itemContainer_.push(std::move(item));
            }
        }

    public:
        using OptPoolItem = folly::Optional<typename OkPgPoolTypedefs::PoolItem>;

        typename OkPgPoolTypedefs::FuturePoolItem
        takeItemFutureWithTimeOut(int mills) {
            std::lock_guard<std::recursive_mutex> lockGuard(poolLock_);
            typename OkPgPoolTypedefs::PromisePoolItem p;
            typename OkPgPoolTypedefs::FuturePoolItem f = p.getFuture();
            if (!itemContainer_.empty()) {
                //if ready queue is ready , return immediately
                typename OkPgPoolTypedefs::PoolItem &itm = itemContainer_.front();
                typename OkPgPoolTypedefs::PoolItem rValItem = itm.moveOut();//magic :P
                itemContainer_.pop();
                p.setValue(std::move(rValItem));
            } else {
                waitingContainer_.push(std::move(p));
            }
            return f;
        }

        OptPoolItem //if mills = -1, wait ,before promise fullfilled
        takeItemWithTimeOut(int mills) {
            typename OkPgPoolTypedefs::FuturePoolItem f = takeItemFutureWithTimeOut(mills);
            OptPoolItem returnOpt;
            try {
                typename OkPgPoolTypedefs::PoolItem poolItem =
                        mills == -1 ? f.get() : f.get(folly::Duration(mills));
                returnOpt.assign(std::move(poolItem));
                return returnOpt;
            } catch (const folly::TimedOut &errTimeOut) {
                LOG(INFO) << "takeItemWithTimeOut timeOut Reason:\t" << errTimeOut.what();
            }
            return returnOpt;
        }

        OptPoolItem takeItem() {
            return takeItemWithTimeOut(-1);
        }

        //return item 2 pool
        OkPool &returnItem(typename OkPgPoolTypedefs::PoolItem &item) {
            //CHECK , not managed by this pool!
            CHECK(item.parentPool_ == this) << "item not host by this pool!";
            if (!waitingContainer_.empty()) {
                typename OkPgPoolTypedefs::PromisePoolItem &p = waitingContainer_.front();
                p.setValue(item.moveOut());
                waitingContainer_.pop();
            } else {
                itemContainer_.push(item.moveOut());
            }
            return *this;
        }
    };
}


#endif //OKPG_OKPGCONPOOL_H
